﻿// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
using System;
using System.Collections.Generic;

namespace Tierless.Framework.Server.Data
{
	/// <summary>
	/// Indicates that the assembly on which it appears contains server entity type model classes.
	/// </summary>
	[AttributeUsage(AttributeTargets.Assembly)]
	public sealed class ServerEntityTypeAssemblyAttribute : Attribute
	{ }

	/// <summary>
	/// Indicates that the class on which it appears provides a server entity type implementation.
	/// </summary>
	[AttributeUsage(AttributeTargets.Class, Inherited = true)]
	public sealed class ServerEntityTypeAttribute : Attribute
	{
		/// <summary>
		/// Initializes a new instance of the ServerEntityTypeAttribute class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		public ServerEntityTypeAttribute(string entityTypeName)
		{
			EntityTypeName = entityTypeName;
		}

		/// <summary>
		/// Gets the name of the entity type.
		/// </summary>
		public string EntityTypeName { get; private set; }
	}

	/// <summary>
	/// Indicates that the class on which it appears provides a server entity command implementation.
	/// </summary>
	[AttributeUsage(AttributeTargets.Class, Inherited = true)]
	public sealed class ServerEntityCommandAttribute : Attribute
	{
		/// <summary>
		/// Initializes a new instance of the ServerEntityCommandAttribute class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		/// <param name="commandName">The name of the command.</param>
		public ServerEntityCommandAttribute(string entityTypeName, string commandName)
		{
			EntityTypeName = entityTypeName;
			CommandName = commandName;
		}

		/// <summary>
		/// Gets the name of the entity type.
		/// </summary>
		public string EntityTypeName { get; private set; }

		/// <summary>
		/// Gets the name of the command.
		/// </summary>
		public string CommandName { get; private set; }
	}
	
	/// <summary>
	/// Provides a factory for obtaining server entity types.
	/// </summary>
	public static class ServerEntityTypeFactory
	{
		private static bool _isPopulated;
		
		private static Dictionary<string, Type> _entityTypes = new Dictionary<string, Type>();
		
		private static Dictionary<string, Dictionary<string, Type>> _entityCommandTypes = new Dictionary<string, Dictionary<string, Type>>();

		private static void RegisterCommandDef(string entityTypeName, string commandName, Type commandType)
		{
			Dictionary<string, Type> commandDefs;
			if (!_entityCommandTypes.TryGetValue(entityTypeName, out commandDefs))
			{
				commandDefs = new Dictionary<string, Type>();
				_entityCommandTypes.Add(entityTypeName, commandDefs);
			}
			
			commandDefs.Add(commandName, commandType);
		}

		/// <summary>
		/// Returns the server entity type for the given entity type name.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		/// <returns>The server entity type instance for the given enity type name.</returns>
		public static ServerEntityType Create(string entityTypeName)
		{
			lock (_entityTypes)
			{
				if (!_isPopulated)
				{
					foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
						if (Attribute.IsDefined(assembly, typeof(ServerEntityTypeAssemblyAttribute)))
							foreach (var type in assembly.GetTypes())
							{
								var attribute = Attribute.GetCustomAttribute(type, typeof(ServerEntityTypeAttribute)) as ServerEntityTypeAttribute;
								if (attribute != null)
									_entityTypes.Add(attribute.EntityTypeName, type);
									
								var commandDefAttribute = Attribute.GetCustomAttribute(type, typeof(ServerEntityCommandAttribute)) as ServerEntityCommandAttribute;
								if (commandDefAttribute != null)
									RegisterCommandDef(commandDefAttribute.EntityTypeName, commandDefAttribute.CommandName, type);
							}
						
					_isPopulated = true;
				}
				
				ServerEntityType entityType = null;
				
				Type entityTypeType;
				_entityTypes.TryGetValue(entityTypeName, out entityTypeType);
				if (entityTypeType != null)
					entityType = (ServerEntityType)Activator.CreateInstance(entityTypeType, entityTypeName);
				else
					entityType = new ServerEntityType(entityTypeName);
					
				Dictionary<string, Type> commandDefs;
				if (_entityCommandTypes.TryGetValue(entityTypeName, out commandDefs))
					foreach (var pair in commandDefs)
						entityType.AddCommandDef((ServerEntityCommand)Activator.CreateInstance(pair.Value, entityTypeName, pair.Key));
					
				return entityType;
			}
		}
	}
}
